package org.fioma.tree.suffixTree.memory.bytes;

import org.fioma.tree.suffixTree.memory.Memory;

public class MemoryBytes implements Memory {

	// label: 6 bytes (4+2)
	// parent : 5 bytes
	// children: 20 bytes (4 x 5)

	public final static int MAXIMUM_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	protected long PARENT_POSITION = 6;
	protected long LABEL_INDEX_POSITION = 0;
	protected long LABEL_START_POSITION = 4;
	protected long LABEL_LENGTH_POSITION = 5;
	protected long CHILDREN_POSITION = 11;

	protected long LEAF_SIZE = 11;
	protected long INTERNAL_SIZE = 31;
	protected int LABEL_INDEX_INITIAL = 0x80000000;

	private static final int CHILD_ADDRESS_SIZE = 5;

	// Queue<byte[]> labelPool = new LinkedList<byte[]>();
	public byte[][] nodes;
	long cursor = 1;
	long totalBytes;
	int arraySize;

	public long leafsQtty = 0;
	public long internalQtty = 0;

	public MemoryBytes(long totalBytes) {
		int qttyArrays = (int) (totalBytes / MAXIMUM_ARRAY_SIZE) + 1;
		arraySize = (int) (totalBytes / qttyArrays) + 1;
		this.totalBytes = ((long) qttyArrays) * ((long) arraySize);
		nodes = new byte[qttyArrays][arraySize];
	}

	@Override
	public long allocMemoryInternalNode() {
		internalQtty++;
		long ret = cursor;
		cursor = cursor + INTERNAL_SIZE;
		if (cursor < totalBytes) {
			return ret;
		} else {
			throw new RuntimeException("Booom!!! I need more memory.");
		}
	}

	@Override
	public long allocMemoryLeafNode() {
		leafsQtty++;
		long ret = cursor;
		cursor = cursor + LEAF_SIZE;
		if (cursor < totalBytes) {
			setLabelIndex(ret, LABEL_INDEX_INITIAL);
			return ret;
		} else {
			throw new RuntimeException("Booom!!! I need more memory.");
		}
	}

	@Override
	public long getParentAddress(long pos) {
		return get5Bytes(pos + PARENT_POSITION);
	}

	@Override
	public int getLabelIndex(long pos) {
		int ret = get4Bytes(pos + LABEL_INDEX_POSITION);
		if (ret < 0) {
			ret ^= LABEL_INDEX_INITIAL;
		}
		return ret;
	}

	@Override
	public int getLabelStart(long pos) {
		return getByte(pos + LABEL_START_POSITION);
	}

	@Override
	public int getLabelLength(long pos) {
		return getByte(pos + LABEL_LENGTH_POSITION);
	}

	@Override
	public long getChildAddress(long pos, byte letter) {
		pos = pos + CHILDREN_POSITION + (CHILD_ADDRESS_SIZE * letter);
		return get5Bytes(pos);
	}

	@Override
	public long[] getChildsAddresses(long pos) {
		pos = pos + CHILDREN_POSITION;
		long[] ret = new long[4];
		for (int i = 0; i < 4; i++) {
			ret[i] = get5Bytes(pos + (CHILD_ADDRESS_SIZE * i));
		}
		return ret;
	}

	@Override
	public void setParentAddress(long pos, long parentAddress) {
		set5Bytes(pos + PARENT_POSITION, parentAddress);
	}

	@Override
	public void setLabelIndex(long pos, int index) {
		if (isLeaf(pos)) {
			index |= LABEL_INDEX_INITIAL;
		}
		set4Bytes(pos + LABEL_INDEX_POSITION, index);
	}

	@Override
	public void setLabelStart(long pos, int start) {
		setByte(pos + LABEL_START_POSITION, (byte) start);
	}

	@Override
	public void setLabelLength(long pos, int length) {
		setByte(pos + LABEL_LENGTH_POSITION, (byte) length);
	}

	@Override
	public void setChildAddress(long pos, byte letter, long childAddress) {
		set5Bytes(pos + CHILDREN_POSITION + (CHILD_ADDRESS_SIZE * letter), childAddress);
	}

	@Override
	public boolean isLeaf(long pos) {
		return get4Bytes(pos + LABEL_INDEX_POSITION) < 0;
	}

	public long get5Bytes(long pos) {
		long ret = 0;
		for (int i = 0; i < 5; i++) {
			ret <<= 8;
			ret |= (long) getByte(pos + i) & 0xFF;
		}
		return ret;
	}

	public void set5Bytes(long pos, long value) {
		setByte(pos, (byte) (value >>> 32));
		setByte(pos + 1, (byte) (value >>> 24));
		setByte(pos + 2, (byte) (value >>> 16));
		setByte(pos + 3, (byte) (value >>> 8));
		setByte(pos + 4, (byte) value);
	}

	public int get4Bytes(long pos) {
		int ret = 0;
		for (int i = 0; i < 4; i++) {
			ret <<= 8;
			ret |= (int) getByte(pos + i) & 0xFF;
		}
		return ret;
	}

	public void set4Bytes(long pos, int value) {
		setByte(pos, (byte) (value >>> 24));
		setByte(pos + 1, (byte) (value >>> 16));
		setByte(pos + 2, (byte) (value >>> 8));
		setByte(pos + 3, (byte) value);
	}

	public byte getByte(long pos) {
		int i = (int) (pos / arraySize);
		int j = (int) (pos % arraySize);
		return nodes[i][j];
	}

	public void setByte(long pos, byte value) {
		int i = (int) (pos / arraySize);
		int j = (int) (pos % arraySize);
		nodes[i][j] = value;
	}

	public static int toInt(byte[] bytes, int offset) {
		int ret = 0;
		for (int i = 0; i < 4 && i + offset < bytes.length; i++) {
			ret <<= 8;
			ret |= (int) bytes[i + offset] & 0xFF;
		}
		return ret;
	}

	public static long toLong(byte[] bytes, int offset) {
		long ret = 0;
		for (int i = 0; i < 5 && i + offset < bytes.length; i++) {
			ret <<= 8;
			ret |= (long) bytes[i + offset] & 0xFF;
		}
		return ret;
	}

	public static final byte[] intToByteArray(int value) {
		return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value };
	}

	public static final byte[] longToByteArray(long value) {
		return new byte[] { (byte) (value >>> 32), (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value };
	}

	@Override
	public long getCursor() {
		return cursor;
	}

}
